//
// file : xmlparser.c
// date : 12/04/04
//

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>

#include "data_xml.h"


static void* xml_alloc(size_t siz)
{
    return calloc(1, ((siz+15)/16)*16);
}

static void xml_free(void *p)
{
    free(p);
}

static xmln_t  xmllen(xmlc_t *x)
{
    return (xmln_t)strlen((char*)x);
}


xmlc_t* xml_dup(xmlc_t *str)
{
    xmlc_t *x;
    xmln_t len;

    len = xmllen(str)+1;

    x = (xmlc_t*)xml_alloc(len);

    memcpy(x, str, len);

    return x;
}

/****************************************************************************
**                                                                         **
** int xml_caseprefix(u8_t *prefix, u8_t *str)                             **
**                                                                         **
**   if a null-terminated string 'prefix' is a case-sensitive prefix       **
**   of string 'str' it returns length of prefix, else 0                   **
**                                                                         **
****************************************************************************/
int xml_caseprefix(xmlc_t *prefix, xmlc_t *str)
{
    xmlc_t *c;
    
    if(! prefix || ! str ) return 0;

    for(c = prefix; *c; c++)
    {
        if(*c != *str)
        {
            break;
        }
        
        str++;
    }
    
    return ( (*c) ? 0 : (c - prefix) ) ;   
}

/****************************************************************************
**                                                                         **
** int xml_prefix(u8_t *prefix, u8_t *str)                                 **
**                                                                         **
**   if a null-terminated string 'prefix' is a case-insensitive prefix     **
**   of string 'str' it returns length of prefix, else 0                   **
**                                                                         **
****************************************************************************/
int xml_prefix(xmlc_t *prefix, xmlc_t *str)
{
    xmlc_t *c;
    
    if(! prefix || ! str ) return 0;

    for(c = prefix; *c; c++)
    {
        if(Z_TO_LOWER(*c) != Z_TO_LOWER(*str))
        {
            break;
        }
        
        str++;
    }
    
    return ( (*c) ? 0 : (c - prefix) ) ;   
}

/****************************************************************************
**                                                                         **
** int xml_match(xmlc_t *to, xmlc_t *fr)                                   **
**                                                                         **
**   return non-zero is both strings match case-insensitivively            **
**   otherwise return 0                                                    **
**                                                                         **
****************************************************************************/
int xml_match(xmlc_t *to, xmlc_t *fr)
{
    if( to == fr ) return 1;
    
    if(! to || ! fr ) return 0;

    for(; *to; to++, fr++)
    {
        if(Z_TO_LOWER(*to) != Z_TO_LOWER(*fr))
        {
            return 0;
        }
    }
    
    return ( ! (*fr) ) ;   
}

/////////////////////////////////////////////////////////////////////////////


static void trio_clear(trio_t *trio)
{
    if( trio->tag ) xml_free(trio->tag);

    if( trio->val ) xml_free(trio->val);

    memset(trio, 0, sizeof(*trio));
}


//
// 'trios' is an array of 'trio_t' terminated by an empty item
//
static trio_t *trio_get_by_tag(xmlc_t *tag, trio_t *trios)
{
    if( 0 == trios ) return 0;

    for( ; GOOD_TRIO(trios); trios++ )
    {
        if( xml_match(tag, trios->tag) )
        {
            return trios;
        }
    }

    return 0;
}

/*
static trio_t *trio_get_by_idx(xmln_t idx, trio_t *trios)
{
    if( 0 == trios ) return 0;

    for( ; GOOD_TRIO(trios); trios++ )
    {
        if( idx == trios->idx )
        {
            return trios;
        }
    }

    return 0;
}
*/

/////////////////////////////////////////////////////////////////////////////

xmlpc_t*  xmlpc_init(void)
{
    xmlpc_t *pc;

    // make a parser context
    pc = xml_alloc(sizeof(*pc));

    pc->path = xml_alloc(sizeof(*(pc->path)));

    pc->path->dep = XML_MAX_DEPTH; // could be larger

    pc->msg_len = XMLBUF_LEN; // could be larger

    return pc;
}

void xmlpc_reset(xmlpc_t *pc)
{
    if( pc->path )
    {
        xmln_t  i;
    
        // free strings
        for( i = 0; i < pc->path->dep; i++ )
        {
            trio_clear( pc->path->trio );
        }

        pc->path->pnt = 0;
    }

    pc->msg_pnt = 0;
}

void xmlpc_free(xmlpc_t *pc)
{
    if( 0 == pc ) return;

    xmlpc_reset(pc);

    if( pc->path )
    {
        xml_free(pc->path);
    }

    xml_free(pc);
}



/////////////////////////////////////////////////////////////////////////////

// read a '<'
static long xml_tag_open(xmlpc_t *pc)
{
    path_t *path = pc->path;
    trio_t *trio;

    trio = path->trio + path->pnt;

    if( trio->tag && pc->msg_pnt )
    {
        // there is a value associated with current tag
        trio->val = xml_dup(pc->msg_buf);

        // reset msg buffer
        pc->msg_pnt    = 0;
        pc->msg_buf[0] = '\0';
    }

    #ifdef _DEBUG
    assert(pc->msg_pnt==0);
    #endif

    return 1;
}

// when encounter a '>'
static long xml_tag_close(xmlpc_t *pc)
{
    path_t *path = pc->path;
    trio_t *trio;
    long    cnt;

    trio = path->trio + path->pnt;

    cnt = 0;

    if( 0 == pc->msg_pnt )
    {
        // must not be the first character
        return -65;
    }

    if( pc->msg_buf[0] != '/' )
    {
        // a new opening tag

        if( trio->tag )  // if not the root
        {
            // next
            path->pnt += 1;

            if( path->pnt >= path->dep - 1 ) // last one must be empty
            {
                return -66; // path overflow
            }

            trio += 1; // pointing to next in array
        }

        if( pc->msg_buf[pc->msg_pnt-1] == '/' )
        {
            // singleton node

            pc->msg_buf[pc->msg_pnt-1] = '\0'; 

            trio->flg |= TRIO_CLOSED;

            cnt = 1;
        }

        trio->tag = xml_dup(pc->msg_buf);
    }
    else
    {
        // search for matching opening tag
        if( 0 == (trio = trio_get_by_tag(pc->msg_buf+1, path->trio)) )
        {
            return -67; // no match opening tag
        }

        // we allow a closing tag to close multiple tags
        for(; GOOD_TRIO(trio); trio++ )
        {
            trio->flg |= TRIO_CLOSED; 
            cnt += 1;
        }
    }


    // reset msg buffer
    pc->msg_pnt    = 0;
    pc->msg_buf[0] = '\0';

    return cnt;
}

// 
// called when some tags are closed
//
void xml_tag_call(long cnt, xmlio_t *in)
{
    xmlpc_t *pc;
    path_t  *path;
    xmln_t   j;

    pc = in->ctx;

    path = pc->path;

    #ifdef _DEBUG
    assert((xmln_t)cnt <= path->pnt+1);
    #endif

    if( cnt == 0 )  // opening
    {
        if( in->xml_node )
        {
            in->xml_node(in);  // entering a node
        }
    }
    else // clocing
    {
        if( in->xml_path )
        {
            in->xml_path(in);  // closing a node
        }
    }

    // clear node if it is closed - from bottum up
    for(j = path->pnt; cnt; j--, cnt--)
    {
        if( path->trio[j].flg & TRIO_CLOSED )
        {
            // clear
            trio_clear(path->trio + j);
        }
    }

    // this is tricky: if j >= 0, then 'path->pnt' points
    // to a trio with a tag in it; otherwise 'path->pnt' 
    // points to an empty trio
    path->pnt = (j>=0 ? j : 0);

}

//
// this function is call when a new character is available
// the function in->get_xmlc() is ignored
//
long xml_feed(xmlc_t c, xmlio_t *in)
{
    long cnt;

    xmlpc_t *pc;

#ifdef _DEBUG
    assert( in->ctx );
#endif

    pc = in->ctx;

    switch( c )
    {
    case '<':

        return xml_tag_open(pc);

    case '>':

        if( in->fin && xml_match(in->fin, pc->msg_buf) )
        {
            // endof xml message
            xmlpc_reset(pc);

            // tell caller about it
            if( in->xml_end )
            {
                in->xml_end(in);
            }

            in->msg_id += 1;

            return 0;
        }

        cnt = xml_tag_close(pc);
        
        if( cnt >= 0 ) 
        {
            xml_tag_call(cnt, in);
        }

        break;

    default:
        if( isprint( c ) ) // ignore control characters
        {
            // push it
            if( pc->msg_pnt < pc->msg_len )
            {
                pc->msg_buf[pc->msg_pnt++] = c;
                pc->msg_buf[pc->msg_pnt] = '\0';
            }
            else
            {
                return (-68); // message overflow
            }
        }
        break;
    }

    return 0x1UL;
}


/////////////////////////////////////////////////////////////////////////////

//
// this blocking call in that it will not return until
// the end of xml message or get_xmlc() returns EOF
//
long xml_proc(xmlio_t *in)
{
    return 0;
}

